﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RootfusHostView;
using System.Windows;
using System.Diagnostics;

namespace RootfusCoreComponents
{
    /// <summary>
    /// Wraps the supplied plugin. This is what everyone else should
    /// be dealing with, rather than directly with the plug-in. We handle:
    ///     -- The connection status between various items
    ///     -- Saving and loading from a file
    /// </summary>
    public class RootfusSurfaceItemWrapper : DependencyObject
    {
        /// <summary>
        /// The plug-in representation of the time.
        /// </summary>
        private RootfusSurfaceItem _item;

        /// <summary>
        /// The addin name that created this guy.
        /// </summary>
        private string _addin_name;

        /// <summary>
        /// Keep track of how many of these guys get created. We do this because it will help us
        /// make unique names for the user.
        /// </summary>
        static int _item_count = 0;

        /// <summary>
        /// Capture a particular plug-in for use through-out the application. :-)
        /// </summary>
        /// <param name="item"></param>
        public RootfusSurfaceItemWrapper(RootfusSurfaceItem item, string english_name, string addin_name)
        {
            _item = item;
            _addin_name = addin_name;
            _item.SetCallback(new AddinCallback(this));
            SetValue(DownstreamConnectedSurfaceItemsProperty, new List<RootfusSurfaceItemWrapper>());
            UpdateInputPinConfig();

            ///
            /// Configure our name. This is stricktly so the user can
            /// see what is going on. At least when this comment was written, it wasn't used
            /// for anything internal.
            ///

            UserName = english_name + " " + _item_count.ToString();
            _item_count += 1;
        }

        /// <summary>
        /// Returns the addin name for the item
        /// </summary>
        public string AddInName
        {
            get
            {
                return _addin_name;
            }
        }

        /// <summary>
        /// The property definition for UserName. Will get used for display purposes, I suspect. :-)
        /// </summary>
        public static readonly DependencyProperty UserNameProperty = DependencyProperty.Register(
            "UserName",
            typeof(string),
            typeof(RootfusSurfaceItemWrapper));

        /// <summary>
        /// Get or set the username for this particular object. This is what the user gets to see when
        /// they are trying to figure out what is going on. Internally, we use pointers.
        /// </summary>
        public string UserName
        {
            get { return (string)GetValue(UserNameProperty); }
            set { SetValue(UserNameProperty, value); }
        }

        /// <summary>
        /// Returns the default UI element -- the one to display on the main
        /// surface most of the time.
        /// </summary>
        public UIElement SurfaceUI
        {
            get { return _item.SurfaceUI; }
        }

        /// <summary>
        /// Returns a config UI -- or null if there is none.
        /// </summary>
        public UIElement ConfigUI
        {
            get { return _item.ConfigUI; }
        }

        /// <summary>
        /// Reports the set of output pins for this guy.
        /// </summary>
        public IList<string> OutputPinNames
        {
            get { return _item.OutputPinNames; }
        }

        /// <summary>
        /// Keep track of the input pins.
        /// </summary>
        private List<PinConnection> _input_pin_configuration;

        /// <summary>
        /// Reports the set of input pins for this guy.
        /// </summary>
        public IList<PinConnection> InputPinConfiguration
        {
            get { return _input_pin_configuration; }
        }

        /// <summary>
        /// Returns true if no other item is feeding this item any data!
        /// </summary>
        public bool HasNoIncommingConnections
        {
            get
            {
                return _input_pin_configuration.All(x => !x.HasInputs);
            }
        }

        /// <summary>
        /// Gets true if there is a settings UI availible for this guy.
        /// </summary>
        public bool HasSettingsUI
        {
            get { return _item.HasSettingsUI; }
        }

        /// <summary>
        /// Returns true if we are allowed to resize the item. This is not allowed
        /// to change over the lifetime of the item. :-)
        /// </summary>
        public bool ResizeAllowed
        {
            get { return _item.SurfaceUIResizeAllowed; }
        }

        /// <summary>
        /// Try a new size out...
        /// </summary>
        /// <param name="p"></param>
        /// <param name="p_2"></param>
        public void RequestResize(double delta_width, double delta_height)
        {
            _item.RequestResize (delta_width, delta_height);
        }
        /// <summary>
        /// The item or something has changed. Erase and re-start the pin definitinos.
        /// </summary>
        private void UpdateInputPinConfig()
        {
            _input_pin_configuration = new List<PinConnection>();
            foreach (var p in _item.InputPins)
            {
                _input_pin_configuration.Add(PinConnection.Create(p));
            }
        }

        /// <summary>
        /// We think some input has changed. This item needs to re-do itself.
        /// </summary>
        public void Update()
        {
            _item.Update();
            Updated = true;
        }

        public bool Updated { get; set; }

        /// <summary>
        /// We want to get the ROOT object for a particular bin. Sort out who is connected
        /// to that pin and fowrad it back to the guy! Return NULL if there is nothing there...
        /// </summary>
        /// <param name="pin"></param>
        /// <returns></returns>
        internal ROOTObjectSpec[] GetConnectedPinDataAsROOTObject(PinDefinition pin)
        {
            ///
            /// Find the connection for this pin
            /// 

            PinConnection acon = _input_pin_configuration.Find(p => pin.Name == p.Name);
            if (acon == null)
            {
                return null;
            }

            ///
            /// Hide the difference between multi and single pins.
            /// 

            List<ROOTObjectSpec> returns = new List<ROOTObjectSpec>();
            switch (acon.ConnectionType)
            {
                case PinConnectionType.PinSingle:
                    PinConnectionSingle spin = acon as PinConnectionSingle;
                    if (spin.ValidConnection) {
                        returns.Add(spin.PinSource.Item.GetPinDataAsROOTObject(spin.PinSource.Pin));
                    }
                    break;
                case PinConnectionType.PinInfinite:
                    PinConnectionInfinite ipin = acon as PinConnectionInfinite;
                    foreach (var item in ipin.PinSources)
                    {
                        if (item.Item != null && item.Pin != null)
                        {
                            returns.Add(item.Item.GetPinDataAsROOTObject(item.Pin));
                        }
                    }
                    break;
                default:
                    break;
            }

            return returns.ToArray();
        }

        /// <summary>
        /// Get a listing of names that we can use for UI that are the names of these pins.
        /// </summary>
        /// <param name="pin"></param>
        internal string[] GetNamesOfConnectedPins(PinDefinition pin)
        {
            ///
            /// Find the connection for this pin
            /// 

            PinConnection acon = _input_pin_configuration.Find(p => pin.Name == p.Name);
            if (acon == null)
            {
                return null;
            }

            ///
            /// Hide the difference between multi and single pins.
            /// 

            switch (acon.ConnectionType)
            {
                case PinConnectionType.PinSingle:
                    PinConnectionSingle spin = acon as PinConnectionSingle;
                    if (spin.ValidConnection)
                    {
                        return new string[] { spin.PinSource.Item.UserName + " - " + spin.PinSource.Pin };
                    }
                    break;
                case PinConnectionType.PinInfinite:
                    PinConnectionInfinite ipin = acon as PinConnectionInfinite;
                    var names = from item in ipin.PinSources
                                where item.Item != null && item.Pin != null
                                select item.Item.UserName + " - " + item.Pin;
                    return names.ToArray();
                default:
                    break;
            }
            return new string[0];
        }

        /// <summary>
        /// We are called by someone who wants some root data from our addin.
        /// </summary>
        /// <param name="pinDefinition"></param>
        /// <returns></returns>
        private ROOTObjectSpec GetPinDataAsROOTObject(string pinName)
        {
            if (pinName == null)
            {
                return null;
            }
            return _item.GetPinDataAsROOTObject(pinName);
        }

        /// <summary>
        /// Configure one of the pins correctly.
        /// </summary>
        /// <param name="p"></param>
        /// <param name="source_wrapper"></param>
        /// <param name="source_pin"></param>
        internal void SetPinConfig(string p, List<IncomingPinConnectionInfo> pin_landings)
        {
            PinConnection c = InputPinConfiguration.First(x => x.Name == p);
            if (c != null)
            {
                switch (c.ConnectionType)
                {
                    case PinConnectionType.PinInfinite:
                        PinConnectionInfinite ip = c as PinConnectionInfinite;
                        foreach (var pin_landing in pin_landings)
                        {
                            ip.PinSources.Add(pin_landing);
                        }
                        break;
                    case PinConnectionType.PinSingle:
                        if (pin_landings.Count > 0)
                        {
                            Debug.Assert(pin_landings.Count == 1);
                            PinConnectionSingle sp = c as PinConnectionSingle;
                            sp.PinSource = pin_landings[0];
                        }
                        break;
                    default:
                        throw new Exception("Unimplemented!");
                }
            }
        }

        /// <summary>
        /// Called when we "close" the config ui.
        /// </summary>
        public void ConfigDone()
        {
            _item.ConfigDone();
        }

        /// <summary>
        /// The output pins have changed somehow -- propagate the Update on downstream wrappers.
        /// </summary>
        internal void OutputPinsChanged()
        {
            foreach (var w in DownstreamConnectedSurfaceItems)
            {
                w.UpdateIfUsingItem(this);
            }
        }

        /// <summary>
        /// Update ourselves only if the calling item is being used by one of our incoming connections.
        /// </summary>
        /// <param name="rootfusSurfaceItemWrapper"></param>
        private void UpdateIfUsingItem(RootfusSurfaceItemWrapper item)
        {
            bool inuse = _input_pin_configuration.Any(c => c.ConnectedTo(item));
            if (inuse)
            {
                Update();
            }
        }

        /// <summary>
        /// Get the list that holds the items downstream of us that we are connected to.
        /// </summary>
        public List<RootfusSurfaceItemWrapper> DownstreamConnectedSurfaceItems
        {
            get { return (List<RootfusSurfaceItemWrapper>)GetValue(DownstreamConnectedSurfaceItemsProperty); }
        }

        // Using a DependencyProperty as the backing store for DownstreamConnectedSurfaceItems.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DownstreamConnectedSurfaceItemsProperty =
            DependencyProperty.Register("DownstreamConnectedSurfaceItems", typeof(List<RootfusSurfaceItemWrapper>), typeof(RootfusSurfaceItemWrapper),
            new UIPropertyMetadata(null));


        private string _addin_config_xml = null;

        /// <summary>
        /// Gets the XML for the items configuration.
        /// </summary>
        public string ItemConfigXML
        {
            get { return _addin_config_xml; }
            set
            {
                _addin_config_xml = value;
                _item.SetConfigXML(_addin_config_xml);
            }
        }

        /// <summary>
        /// The addin has updated its info. We should save it and mark ourselves as modified.
        /// </summary>
        /// <param name="xmltext"></param>
        internal void UpdateSaveInformation(string xmltext)
        {
            _addin_config_xml = xmltext;
            OnConfigurationChanged();
        }

        /// <summary>
        /// The callback that occurs when something in here changes.
        /// </summary>
        public delegate void ConfigurationChangedCallback();

        /// <summary>
        /// Fires when somethign about the config in this guy has changed - and may need saving!
        /// </summary>
        public event ConfigurationChangedCallback ConfigurationChanged;

        /// <summary>
        /// Override if you want to capture an event.
        /// </summary>
        protected virtual void OnConfigurationChanged()
        {
            if (ConfigurationChanged != null)
            {
                ConfigurationChanged();
            }
        }

        /// <summary>
        /// A particular item is going away. Make sure to un-use it if it is currently being used.
        /// </summary>
        /// <param name="rootfusSurfaceItemWrapper"></param>
        public void RemoveIncommingConnections(RootfusSurfaceItemWrapper rootfusSurfaceItemWrapper)
        {
            bool altered = false;
            foreach (var connection in InputPinConfiguration)
            {
                if (connection.ConnectedTo(rootfusSurfaceItemWrapper))
                {
                    altered = true;
                    switch (connection.ConnectionType)
                    {
                        case PinConnectionType.PinInfinite:
                            PinConnectionInfinite ip = connection as PinConnectionInfinite;
                            var pins_using_item = from pin_input in ip.PinSources
                                                  where pin_input.Item == rootfusSurfaceItemWrapper
                                                  select pin_input;
                            foreach (var bad_pin in pins_using_item.ToArray())
                            {
                                ip.PinSources.Remove(bad_pin);
                            }
                            break;
                        case PinConnectionType.PinSingle:
                            PinConnectionSingle sp = connection as PinConnectionSingle;
                            sp.PinSource.Item = null;
                            sp.PinSource.Pin = null;
                            break;
                    }
                }
            }

            ///
            /// If we changed things, then we need to update the display!
            /// 

            if (altered)
            {
                Update();
            }
        }
    }
}
